package cn.jofei.core;

import cn.jofei.common.ano.Bean;
import cn.jofei.common.ano.Component;
import cn.jofei.common.ano.LazyLoad;
import cn.jofei.common.ano.PackageScan;
import cn.jofei.common.util.Utils;
import cn.jofei.core.bean.BeanDefined;
import cn.jofei.core.bean.BeanFactory;
import cn.jofei.core.starter.AppFunctionStarter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 低配版启动器
 *
 * @author Jofei Bao
 * @version ngbao v1.0.2
 * @date 2022/7/11 13:36
 */
public class AppRunner extends BeanFactory {

    public static final Logger log = LoggerFactory.getLogger(AppRunner.class);

    private AppRunner() {
        throw new RuntimeException("不能被实例化");
    }

    public static void run(Class<?> clazz) {
        run(clazz, null);
    }

    public static void run(Class<?> clazz, String[] args) {
        try {
            String packagePath = clazz.getPackage().getName();
            //用户自定义包扫描路径
            PackageScan scan = clazz.getAnnotation(PackageScan.class);
            if (Utils.nonNull(scan) && Utils.nonNull(scan.pack())) {
                packagePath = scan.pack();
            }
            List<Class<?>> classList = PackageScanner.scanClass(packagePath);

            List<String> prepareBean = new ArrayList<>();
            //功能启动器
            List<AppFunctionStarter> initStarters = new ArrayList<>();
            //初始化功能启动器启动器
            for (Class<?> aClass : classList) {
                Class<?>[] interfaces = aClass.getInterfaces();
                if (interfaces.length > 0 && Arrays.stream(interfaces).allMatch(AppFunctionStarter.class::equals)) {
                    Deprecated deprecated = aClass.getDeclaredAnnotation(Deprecated.class);
                    if (deprecated == null) {
                        AppFunctionStarter o = (AppFunctionStarter) aClass.getDeclaredConstructor().newInstance();
                        initStarters.add(o);
                    }
                }
            }

            //初始化 bean
            initBean(classList, initStarters);

            //依赖注入
            for (Map.Entry<String, Object> entry : BEAN_NAME_BEAN_MAPPING.entrySet()) {
                Object o = entry.getValue();

                String instanceFail = Invoker.initInstance(o);

                //注入后的狗子
                for (AppFunctionStarter starter : initStarters) {
                    starter.afterInject(o);
                }

                if (instanceFail != null) {
                    prepareBean.add(instanceFail);
                }
            }

            //注入失败尝试重新注入
            Iterator<String> iterator = prepareBean.iterator();
            while (iterator.hasNext()) {
                Object o = BEAN_NAME_BEAN_MAPPING.get(iterator.next());
                if (o != null) {
                    Invoker.initInstance(o);
                    //注入后的狗子
                    for (AppFunctionStarter starter : initStarters) {
                        starter.afterInject(o);
                    }
                }
                iterator.remove();
            }

            //启动功能
            for (AppFunctionStarter starter : initStarters) {
                starter.start();
            }
        } catch (Exception e) {
            log.error("容器初始化失败", e);
        }
    }

    private static void initBean(List<Class<?>> classList, List<AppFunctionStarter> initStarters) throws IllegalAccessException, InvocationTargetException {
        for (Class<?> aClass : classList) {
            Annotation[] annotations = aClass.getDeclaredAnnotations();
            if (annotations.length == 0 || aClass.isAnnotation()) {
                continue;
            }
            //Component 注解 或 Component 类型的注解
            if (Arrays.stream(annotations).anyMatch(e -> Component.class.equals(e.annotationType()) || Arrays.stream(e.annotationType().getDeclaredAnnotations()).anyMatch(b -> b.annotationType().equals(Component.class)))) {
                String name = Invoker.beanName(aClass);
                Constructor<?> construct = aClass.getDeclaredConstructors()[0];
                LazyLoad lazyLoad = aClass.getDeclaredAnnotation(LazyLoad.class);
                //创建bean定义
                BeanDefined beanDefined = new BeanDefined();
                beanDefined.setClazz(aClass);
                beanDefined.setName(name);
                beanDefined.setLazy(lazyLoad != null);
                if (construct.getParameterTypes().length != 0) {
                    beanDefined.setLazy(true);
                    beanDefined.setConstructClasses(construct.getParameterTypes());
                }
                //添加bean定义
                BEAN_BEAN_DEFINED_MAPPING.putIfAbsent(name, beanDefined);
                //实例化
                if (!beanDefined.isLazy()) {
                    Object instance = instance(beanDefined);
                    // Bean 注解初始化 bean
                    for (Method declaredMethod : aClass.getDeclaredMethods()) {
                        Bean bean = declaredMethod.getAnnotation(Bean.class);
                        if (Utils.nonNull(bean)) {
                            Object invoke = declaredMethod.invoke(instance);
                            String beanName = Utils.isNull(bean.name()) ? Invoker.beanName(invoke.getClass()) : bean.name();
                            if (BEAN_NAME_BEAN_MAPPING.putIfAbsent(beanName, invoke) != null) {
                                throw new RuntimeException("Bean名【" + beanName + "】称冲突");
                            } else {
                                BEAN_MAPPING.putIfAbsent(beanName, invoke.getClass());
                                log.info("初始化bean：{}", beanName);
                            }
                        }
                    }
                    //执行实例化后的狗子
                    for (AppFunctionStarter starter : initStarters) {
                        starter.beforeInject(instance);
                    }
                }
            }
        }
    }

    /**
     * 包扫描工具
     *
     * @author Jofei Bao
     * @version ngbao v1.0.2
     * @date 2022年07月10日 22:41
     */
    private static class PackageScanner {
        /**
         * 类加载器
         */
        private static final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        /**
         * 系统相关的文件路径分隔符 \或/
         */
        private static final String FILE_SEPARATOR = File.separator;

        /**
         * 从指定的java包路径下扫描Class对象
         *
         * @param packageName 包路径名,必须从根路径写起。例如“com.test”
         * @return
         * @throws IOException
         */
        public static List<Class<?>> scanClass(String packageName) throws IOException {
            ArrayList<Class<?>> classList = new ArrayList<>();
            for (String className : scanClassName(packageName)) {
                try {
                    classList.add(Class.forName(className));
                } catch (ClassNotFoundException e) {
                    // 由于所有的className都是我们自己扫描而来的。所以不会出现ClassNotFoundException，所以此处将ClassNotFoundException对外屏蔽掉
                    e.printStackTrace();
                }
            }
            return classList;
        }

        /**
         * 从指定的java包路径下扫描className
         *
         * @param packageName 包路径名。例如“com.test”
         * @return
         * @throws IOException
         */
        public static List<String> scanClassName(String packageName) throws IOException {
            // 将包路径分隔符由.替换成ClassLoader可以识别的/
            String packagePath = packageName.replace(".", "/");
            // 通过类加载器获得资源对象
            URL resource = classLoader.getResource(packagePath);

            // 获取资源路径字符串。
            // jar的path例如：file:/D:/WorkProgram/workspace/Test/lib/commons-codec-1.4.jar!/org/apache/commons/codec
            // 文件夹的path例如：/D:/WorkProgram/workspace/Test/bin/test/util
            String path = resource.getPath();
            List<String> classNames = null;
            // 对不同的资源类型分别处理
            if ("jar".equalsIgnoreCase(resource.getProtocol())) {
                // 截取jar包所在路径。例如：/D:/commons-codec-1.4.jar
                String jarPath = path.substring("file:".length(), path.indexOf(packagePath) - 2);
                classNames = scanJarFileClassName(jarPath, packagePath);
            } else {
                // 不是jar包，使用系统路径分隔符进行查找
                classNames = scanDirectoryClassName(path, packagePath.replace("/", FILE_SEPARATOR));
            }
            return classNames;
        }

        /**
         * 从jar包里读取指定包名下的class文件
         *
         * @param jarFilePath jar包所在路径
         * @param packagePath 要扫描的路径
         * @return
         * @throws IOException
         */
        private static List<String> scanJarFileClassName(String jarFilePath, String packagePath) throws IOException {
            // 使用JarInputStream读取jar包内容
            try (JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFilePath))) {
                List<String> nameList = new ArrayList<String>();
                for (JarEntry entry = jarIn.getNextJarEntry(); entry != null; entry = jarIn.getNextJarEntry()) {
                    String name = entry.getName();
                    if (name.startsWith(packagePath) && name.endsWith(".class")) {
                        // java的文件路径分隔符为/，故此处写死
                        nameList.add(name.replace("/", ".").replace(".class", ""));
                    }
                }
                return nameList;
            }
        }

        /**
         * 从文件夹里读取指定的Class文件
         *
         * @param directoryPath 文件夹路径
         * @param packagePath   要扫描的路径
         * @return
         */
        private static List<String> scanDirectoryClassName(String directoryPath, String packagePath) {
            List<String> nameList = new ArrayList<String>();
            for (File file : new File(directoryPath).listFiles()) {
                String path = file.getPath();
                if (file.isDirectory()) {
                    // 如果是文件夹，则递归扫描
                    List<String> scanDirectoryClassName = scanDirectoryClassName(path, packagePath);
                    nameList.addAll(scanDirectoryClassName);
                } else if (file.isFile() && file.getName().endsWith(".class")) {
                    // 从packetPath截取路径，并将系统路径分隔符替换为.
                    nameList.add(path.substring(path.indexOf(packagePath)).replace(FILE_SEPARATOR, ".").replace(".class", ""));
                }
            }
            return nameList;
        }

    }
}
